 /*******************************************************************************
  * Copyright (c) 2000, 2006 IBM Corporation and others.
  * All rights reserved. This program and the accompanying materials
  * are made available under the terms of the Eclipse Public License v1.0
  * which accompanies this distribution, and is available at
  * http://www.eclipse.org/legal/epl-v10.html
  *
  * Contributors:
  * IBM Corporation - initial API and implementation
  *******************************************************************************/
 package org.eclipse.ui.texteditor;

 import java.util.ArrayList ;
 import java.util.HashMap ;
 import java.util.HashSet ;
 import java.util.Hashtable ;
 import java.util.Iterator ;
 import java.util.List ;
 import java.util.Map ;
 import java.util.Set ;
 import java.util.StringTokenizer ;

 import org.osgi.framework.Bundle;

 import org.eclipse.core.resources.IFile;
 import org.eclipse.core.runtime.CoreException;
 import org.eclipse.core.runtime.IConfigurationElement;
 import org.eclipse.core.runtime.IExtensionPoint;
 import org.eclipse.core.runtime.ILog;
 import org.eclipse.core.runtime.IStatus;
 import org.eclipse.core.runtime.Platform;
 import org.eclipse.core.runtime.Status;

 import org.eclipse.ui.editors.text.EditorsUI;

 import org.eclipse.ui.IEditorInput;
 import org.eclipse.ui.PlatformUI;
 import org.eclipse.ui.internal.editors.text.NLSUtility;


 /**
  * This registry manages shared document providers. Document
  * providers are specified in <code>plugin.xml</code> either
  * per name extension or per editor input type. A name extension
  * rule always overrules an editor input type rule. Editor input
  * type rules follow the same rules <code>IAdapterManager</code>
  * used to find object adapters.
  *
  * @see org.eclipse.core.runtime.IAdapterManager
  */
 public class DocumentProviderRegistry {

     /** The registry singleton. */
     private static DocumentProviderRegistry fgRegistry;

     /**
      * Returns the standard document provider registry.
      *
      * @return the default document provider registry
      */
     public static DocumentProviderRegistry getDefault() {
         if (fgRegistry == null)
             fgRegistry= new DocumentProviderRegistry();
         return fgRegistry;
     }


     /** The mapping between name extensions and configuration elements. */
     private Map fExtensionMapping= new HashMap ();
     /** The mapping between editor input type names and configuration elements. */
     private Map fInputTypeMapping= new HashMap ();
     /** The mapping between configuration elements and instantiated document providers. */
     private Map fInstances= new HashMap ();


     /**
      * Creates a new document provider registry and initializes it with the information
      * found in the plug-in registry.
      */
     private DocumentProviderRegistry() {
         initialize();
     }

     /**
      * Reads the comma-separated value of the given configuration element
      * for the given attribute name and remembers the configuration element
      * in the given map under the individual tokens of the attribute value.
      *
      * @param map the map
      * @param element the configuration element
      * @param attributeName the attribute name
      */
     private void read(Map map, IConfigurationElement element, String attributeName) {
         String value= element.getAttribute(attributeName);
         if (value != null) {
             StringTokenizer tokenizer= new StringTokenizer (value, ","); //$NON-NLS-1$
 while (tokenizer.hasMoreTokens()) {
                 String token= tokenizer.nextToken().trim();

                 Set s= (Set ) map.get(token);
                 if (s == null) {
                     s= new HashSet ();
                     map.put(token, s);
                 }
                 s.add(element);
             }
         }
     }

     /**
      * Initializes the document provider registry. It retrieves all implementers of the <code>documentProviders</code>
      * extension point and remembers those implementers based on the name extensions and the editor input
      * types they are for.
      */
     private void initialize() {

         IExtensionPoint extensionPoint;
         extensionPoint= Platform.getExtensionRegistry().getExtensionPoint(EditorsUI.PLUGIN_ID, "documentProviders"); //$NON-NLS-1$

         if (extensionPoint == null) {
             String msg= NLSUtility.format(TextEditorMessages.DocumentProviderRegistry_error_extension_point_not_found, PlatformUI.PLUGIN_ID);
             Bundle bundle = Platform.getBundle(PlatformUI.PLUGIN_ID);
             ILog log= Platform.getLog(bundle);
             log.log(new Status(IStatus.ERROR, PlatformUI.PLUGIN_ID, IStatus.OK, msg, null));
             return;
         }

         IConfigurationElement[] elements= extensionPoint.getConfigurationElements();
         for (int i= 0; i < elements.length; i++) {
             read(fExtensionMapping, elements[i], "extensions"); //$NON-NLS-1$
 read(fInputTypeMapping, elements[i], "inputTypes"); //$NON-NLS-1$
 }
     }

     /**
      * Returns the document provider for the given configuration element.
      * If there is no instantiated document provider remembered for this
      * element, a new document provider is created and put into the cache.
      *
      * @param entry the configuration element
      * @return the document provider for the given entry
      */
     private IDocumentProvider getDocumentProvider(IConfigurationElement entry) {
         IDocumentProvider provider= (IDocumentProvider) fInstances.get(entry);
         if (provider == null) {
             try {
                 provider= (IDocumentProvider) entry.createExecutableExtension("class"); //$NON-NLS-1$
 fInstances.put(entry, provider);
             } catch (CoreException x) {
             }
         }
         return provider;
     }

     /**
      * Returns the first enumerated element of the given set.
      *
      * @param set the set
      * @return the first configuration element in the set or <code>null</code> if none
      */
     private IConfigurationElement selectConfigurationElement(Set set) {
         if (set != null && !set.isEmpty()) {
             Iterator e= set.iterator();
             return (IConfigurationElement) e.next();
         }
         return null;
     }

     /**
      * Returns a shared document provider for the given name extension.
      *
      * @param extension the name extension to be used for lookup
      * @return the shared document provider or <code>null</code>
      */
     public IDocumentProvider getDocumentProvider(String extension) {

         Set set= (Set ) fExtensionMapping.get(extension);
         if (set != null) {
             IConfigurationElement entry= selectConfigurationElement(set);
             return getDocumentProvider(entry);
         }
         return null;
     }

     /**
      * Computes the class hierarchy of the given type. The type is
      * part of the computed hierarchy.
      *
      * @param type the type
      * @return a list containing the super class hierarchy
      */
     private List computeClassList(Class type) {

         List result= new ArrayList ();

         Class c= type;
         while (c != null) {
             result.add(c);
             c= c.getSuperclass();
         }

         return result;
     }

     /**
      * Computes the list of all interfaces for the given list of
      * classes. The interface lists of the given classes are
      * concatenated.
      *
      * @param classes a list of {@link java.lang.Class} objects
      * @return a list with elements of type <code>Class</code>
      */
     private List computeInterfaceList(List classes) {

         List result= new ArrayList (4);
         Hashtable visited= new Hashtable (4);

         Iterator e= classes.iterator();
         while (e.hasNext()) {
             Class c= (Class ) e.next();
             computeInterfaceList(c.getInterfaces(), result, visited);
         }

         return result;
     }

     /**
      * Computes the list of all interfaces of the given list of interfaces,
      * taking a depth-first approach.
      *
      * @param interfaces an array of {@link java.lang.Class} objects denoting interfaces
      * @param result the result list
      * @param visited map of visited interfaces
      */
     private void computeInterfaceList(Class [] interfaces, List result, Hashtable visited) {

         List toBeVisited= new ArrayList (interfaces.length);

         for (int i= 0; i < interfaces.length; i++) {
             Class iface= interfaces[i];
             if (visited.get(iface) == null) {
                 visited.put(iface, iface);
                 result.add(iface);
                 toBeVisited.add(iface);
             }
         }

         Iterator e= toBeVisited.iterator();
         while(e.hasNext()) {
             Class iface= (Class ) e.next();
             computeInterfaceList(iface.getInterfaces(), result, visited);
         }
     }

     /**
      * Returns the configuration elements for the first class in the list
      * of given classes for which configuration elements have been remembered.
      *
      * @param classes a list of {@link java.lang.Class} objects
      * @return an input type mapping or <code>null</code>
      */
     private Object getFirstInputTypeMapping(List classes) {
         Iterator e= classes.iterator();
         while (e.hasNext()) {
             Class c= (Class ) e.next();
             Object mapping= fInputTypeMapping.get(c.getName());
             if (mapping != null)
                 return mapping;
         }
         return null;
     }

     /**
      * Returns the appropriate configuration element for the given type. If
      * there is no configuration element for the type's name, first the list of
      * super classes is searched, and if not successful the list of all interfaces.
      *
      * @param type a {@link java.lang.Class} object
      * @return an input type mapping or <code>null</code>
      */
     private Object findInputTypeMapping(Class type) {

         if (type == null)
             return null;

         Object mapping= fInputTypeMapping.get(type.getName());
         if (mapping != null)
             return mapping;

         List classList= computeClassList(type);
         mapping= getFirstInputTypeMapping(classList);
         if (mapping != null)
             return mapping;

         return getFirstInputTypeMapping(computeInterfaceList(classList));
     }

     /**
      * Returns the shared document for the type of the given editor input.
      *
      * @param editorInput the input for whose type the provider is looked up
      * @return the shared document provider
      */
     public IDocumentProvider getDocumentProvider(IEditorInput editorInput) {

         IDocumentProvider provider= null;

         IFile file= (IFile) editorInput.getAdapter(IFile.class);
         if (file != null)
             provider= getDocumentProvider(file.getFileExtension());

         if (provider == null) {
             Set set= (Set ) findInputTypeMapping(editorInput.getClass());
             if (set != null) {
                 IConfigurationElement entry= selectConfigurationElement(set);
                 provider= getDocumentProvider(entry);
             }
         }

         return provider;
     }
 }

